\datethis


@* Notice.

This program is not yet completed.  It won't even compile yet and has
not been evaluated for valid CWEB syntax.


@* Copying.

Copyright (C) 2015 David McMackins II.

Copyright (C) 2015 Caleb Herbert.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, version 3 only.
  
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Affero General Public License for more details.
  
You should have received a copy of the GNU Affero General Public
License along with this program.  If not, see
<http://www.gnu.org/licenses/>.


@* Introduction.

David McMackns (a.k.a. ``Zerock'', a.k.a. ``VRMac'') wrote a little
tic-tac-toe program for instructing pupils in the C programming
language.  It is a very simple program and has only has six parts.
The basic structure of the program is outlined below.

@c
@<includes@> @/
@<|get_bump| function@> @/
@<|find_winner| function@> @/
@<|get_move| function@> @/
@<|draw_board| function@> @/
@<|is_winner| function@> @/


@* History.

ttt was designed and written in response to a challenge from a
colleague of David McMackins who wrote a console tic-tac-toe game in
C++. The code was (frankly) written sloppily, and the program's
structure severely limited its modularity. Furthermore, it was
dependent on external commands unique to Microsoft operating systems
(such as `cls` to clear the console).

McMackins decided he would rewrite the game using clean C code,
require no outside dependencies (besides the standard C library), and
allow a board of variable size. He achieved a program with significant
improvements in runtime efficiency (measured with controlled tests and
inputs on one machine) which could be recompiled to use different
board sizes.

McMackins is very pleased with the result, and he now uses this as a
test program when developing on new systems.


@* Usefulness

This program makes for an advanced ``hello world'' program for aspiring hackers
that want a good sample to study and change.

The C code complies with GNU and C standards, and it can be compiled and run on
any compliant system. It makes use of several standard library features and
showcases their uses. Since the code is documented using a standard format,
readers can understand a functions purpose before reading it, thus better
understanding the code as a whole.

I used GNU Make to automate compilation not because it is hard (I could easily
have written the instructions in this README file) but as a "hello world" for
Makefiles, since hackers will no doubt need build automation tools for their
more significant software.

Since the source is controlled using Git, I also provided a way for new hackers
to practice using Git and learn the advantages to source control. It also
allows them to easily submit their changes and improvements to me to benefit
future hackers.

Lastly, I included a license file which describes the core ideals of free
(libre) software, which I find to be very important for both new and
experienced computer users everywhere.


@* Includes.

This program includes seven (7) standard C header files for its
functions.  It uses |stdio.h|, the ``standard input/output'' header,
which deals with basic interactions with a textual terminal; |time.h|,
the ``[foo]'' header, which [foo]; |stdlib.h|, the ``standard
library'' header, which [foo]; |string.h|, the ``string'' header,
which [foo]; |stdbool.h|, the ``standard boolean'' header, which
allows one to use words like |true| and |false|; |ctype.h|, the
``[foo]'' header, which [foo]; and finally, |stdint.h|, the ``[foo]''
header, which [foo].

@<includes@>=
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
#include <stdint.h>

#ifndef BOARD_SIZE
/**
 * @brief The square dimensions of the game board (best when >=1 and <=26).
 */
# define BOARD_SIZE 3
#endif


@* |get_bump| function

The source code for |get_bump| is as follows.

@<|get_bump| function@>=
static uint8_t
get_bump (uint8_t s)
{
  uint8_t i = 1;

  while (s > 10)
    {
      ++i;
      s /= 10;
    }

  return i;
}


@* |find_winner| function

The |find_winner| function is as follows.

@<|find_winner| function@>=
static char
find_winner (void)
{
  char players[] = {'X', 'O'};
  uint8_t i;

  for (i = 0; i < 2; ++i)
    if (is_winner (players[i]))
      return players[i];

  return 0;
}


@* |get_move| function

The |get_move| function is as follows.

@<|get_move| function@>=
static struct move
get_move (char player)
{
  char h;
  struct move m = {
    .h = BOARD_SIZE,
    .v = BOARD_SIZE
  };

  printf ("%c's move: ", player);
  scanf ("%c%hhu", &h, &m.v);

  // discard extra input to prevent cheating
  while (getchar () != '\n')
    ;

  m.h = toupper (h) - 'A';

  return m;
}


@* |draw_board| function

The |draw_board| function is as follows.

@<|draw_board| function@>=
static void
draw_board (void)
{
  uint8_t i, j, bump = get_bump (BOARD_SIZE);

  printf ("\n\n\n ");

  for (i = 0; i < bump; ++i)
    putchar (' ');

  for (i = 0; i < BOARD_SIZE; ++i)
    printf (" %c", i + 'A');

  putchar ('\n');

  for (i = 0; i < BOARD_SIZE; ++i)
    {
      printf ("%d", i);

      // make sure board lines up on all rows
      uint8_t b = bump - get_bump (i+1);
      for (j = 0; j < b; ++j)
	putchar (' ');

      printf (" |");

      for (j = 0; j < BOARD_SIZE; ++j)
	printf ("%c|", board[i][j]);

      putchar ('\n');
    }
}


@* |is_winner| function

The |is_winner| function is as follows.

@<|is_winner| function@>=
static bool
is_winner (char player)
{
  int16_t i, j;

  // check rows
  for (i = 0; i < BOARD_SIZE; ++i)
    for (j = 0; j < BOARD_SIZE; ++j)
      {
	if (board[i][j] != player)
	  break;

	if ((BOARD_SIZE - 1) == j)
	  return true;
      }

  // check columns
  for (i = 0; i < BOARD_SIZE; ++i)
    for (j = 0; j < BOARD_SIZE; ++j)
      {
	if (board[j][i] != player)
	  break;

	if ((BOARD_SIZE - 1) == j)
	  return true;
      }

  // check diagonal from top-left to bottom-right
  for (i = 0, j = 0; i < BOARD_SIZE && j < BOARD_SIZE; ++i, ++j)
    {
      if (board[i][j] != player)
	break;

      if ((BOARD_SIZE - 1) == i)
	return true;
    }

  // check diagonal from top-right to bottom-left
  for (i = 0, j = BOARD_SIZE-1; i < BOARD_SIZE && j >= 0; ++i, --j)
    {
      if (board[i][j] != player)
	break;

      if (0 == j)
	return true;
    }

  return false;
}
